/*
 * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.generators.interpreter

import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.generators.util.GeneratorsFileUtil
import org.jetbrains.kotlin.ir.BuiltInOperatorNames
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.IntrinsicConstEvaluation
import org.jetbrains.kotlin.utils.Printer
import kotlin.reflect.full.memberFunctions

import java.io.File

val DESTINATION = File("compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/builtins/IrBuiltInsMapGenerated.kt")

private val integerTypes =
    listOf(PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.INT, PrimitiveType.LONG).map { it.typeName.asString() }
private val fpTypes = listOf(PrimitiveType.FLOAT, PrimitiveType.DOUBLE).map { it.typeName.asString() }
private val numericTypes = PrimitiveType.NUMBER_TYPES.map { it.typeName.asString() }

fun main() {
    GeneratorsFileUtil.writeFileIfContentChanged(DESTINATION, generateMap())
}

fun generateMap(): String {
    val sb = StringBuilder()
    val p = Printer(sb)
    printPreamble(p)

    val unaryOperations = getOperationMap(1).apply {
        this.addAll(getUnsignedConversionOperationMap())
        this += Operation(BuiltInOperatorNames.CHECK_NOT_NULL, listOf("T0?"), customExpression = "a!!")
        this += Operation("toString", listOf("Any?"), customExpression = "a?.toString() ?: \"null\"")
        this += Operation("code", listOf("Char"), customExpression = "(a as Char).code")
        // TODO next operation can be dropped after serialization introduction
        this += Operation("toString", listOf("Unit"), customExpression = "Unit.toString()")
    }

    val binaryOperations = getOperationMap(2) + getBinaryIrOperationMap() + getExtensionOperationMap()

    val ternaryOperations = getOperationMap(3)

    generateInterpretUnaryFunction(p, unaryOperations)
    generateInterpretBinaryFunction(p, binaryOperations)
    generateInterpretTernaryFunction(p, ternaryOperations)

    return sb.toString()
}

private fun printPreamble(p: Printer) {
    p.println(File("license/COPYRIGHT_HEADER.txt").readText())
    p.println()
    p.println("@file:Suppress(\"DEPRECATION\", \"DEPRECATION_ERROR\", \"UNCHECKED_CAST\", \"REDUNDANT_CALL_OF_CONVERSION_METHOD\")")
    p.println()
    p.println("package org.jetbrains.kotlin.ir.interpreter.builtins")
    p.println()
    p.println("import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterMethodNotFoundError")
    p.println("import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy")
    p.println()
    p.println("/** This file is generated by `./gradlew generateInterpreterMap`. DO NOT MODIFY MANUALLY */")
    p.println()
}

private fun generateInterpretUnaryFunction(p: Printer, unaryOperations: List<Operation>) {
    p.println("internal fun interpretUnaryFunction(name: String, type: String, a: Any?): Any? {")
    p.pushIndent()
    p.println("when (name) {")
    p.pushIndent()
    for ((name, operations) in unaryOperations.groupBy(Operation::name)) {
        p.println("\"$name\" -> when (type) {")
        p.pushIndent()
        for (operation in operations.sortedBy { it.typeA.typeSortKey() }) {
            p.println("\"${operation.typeA}\" -> return ${operation.expressionString}")
        }
        p.popIndent()
        p.println("}")
    }
    p.popIndent()
    p.println("}")
    p.println("throw InterpreterMethodNotFoundError(\"Unknown function: \$name(\$type)\")")
    p.popIndent()
    p.println("}")
    p.println()
}

private fun generateInterpretBinaryFunction(p: Printer, binaryOperations: List<Operation>) {
    p.println("internal fun interpretBinaryFunction(name: String, typeA: String, typeB: String, a: Any?, b: Any?): Any? {")
    p.pushIndent()
    p.println("when (name) {")
    p.pushIndent()
    for ((name, operations) in binaryOperations.groupBy(Operation::name)) {
        p.println("\"$name\" -> when (typeA) {")
        p.pushIndent()
        for ((typeA, operationsOnTypeA) in operations.sortedBy { it.typeA.typeSortKey() }.groupBy(Operation::typeA)) {
            val singleOperation = operationsOnTypeA.singleOrNull()
            if (singleOperation != null) {
                // Slightly improve readability if there's only one operation with such name and typeA.
                p.println("\"$typeA\" -> if (typeB == \"${singleOperation.typeB}\") return ${singleOperation.expressionString}")
            } else {
                p.println("\"$typeA\" -> when (typeB) {")
                p.pushIndent()
                for ((typeB, operationsOnTypeB) in operationsOnTypeA.groupBy(Operation::typeB)) {
                    for (operation in operationsOnTypeB.sortedBy { it.typeB.typeSortKey() }) {
                        p.println("\"$typeB\" -> return ${operation.expressionString}")
                    }
                }
                p.popIndent()
                p.println("}")
            }
        }
        p.popIndent()
        p.println("}")
    }
    p.popIndent()
    p.println("}")
    p.println("throw InterpreterMethodNotFoundError(\"Unknown function: \$name(\$typeA, \$typeB)\")")
    p.popIndent()
    p.println("}")
    p.println()
}

private fun generateInterpretTernaryFunction(p: Printer, ternaryOperations: List<Operation>) {
    p.println("internal fun interpretTernaryFunction(name: String, typeA: String, typeB: String, typeC: String, a: Any?, b: Any?, c: Any?): Any {")
    p.pushIndent()
    p.println("when (name) {")
    p.pushIndent()
    for ((name, operations) in ternaryOperations.groupBy(Operation::name)) {
        p.println("\"$name\" -> when (typeA) {")
        p.pushIndent()
        for (op in operations.sortedBy { it.typeA.typeSortKey()}) {
            p.println("\"${op.typeA}\" -> if (typeB == \"${op.typeB}\" && typeC == \"${op.typeC}\") return ${op.expressionString}")
        }
        p.popIndent()
        p.println("}")
    }
    p.popIndent()
    p.println("}")
    p.println("throw InterpreterMethodNotFoundError(\"Unknown function: \$name(\$typeA, \$typeB, \$typeC)\")")
    p.popIndent()
    p.println("}")
    p.println()
}

private data class Operation(
    val name: String,
    private val parameterTypes: List<String>,
    val isFunction: Boolean = true,
    val customExpression: String? = null,
) {
    val typeA: String get() = parameterTypes[0].addKotlinPackage()
    val typeB: String get() = parameterTypes[1].addKotlinPackage()
    val typeC: String get() = parameterTypes[2].addKotlinPackage()

    val expressionString: String
        get() {
            val receiver = castValueParenthesized("a", parameterTypes[0])
            return when {
                name == BuiltInOperatorNames.EQEQEQ -> "if (a is Proxy && b is Proxy) a.state === b.state else a === b"
                customExpression != null -> customExpression
                getIrMethodSymbolByName(name) != null -> {
                    buildString {
                        append(castValueParenthesized("a", parameterTypes[0]))
                        append(" ")
                        append(getIrMethodSymbolByName(name))
                        append(" ")
                        append(castValueParenthesized("b", parameterTypes[0]))
                    }
                }
                else -> buildString {
                    append(receiver)
                    append(".")
                    append(name)
                    if (isFunction) append("(")
                    parameterTypes.withIndex().drop(1).joinTo(this) { (index, type) ->
                        castValue(('a' + index).toString(), type)
                    }
                    if (isFunction) append(")")
                }
            }
        }

    private fun getIrMethodSymbolByName(methodName: String): String? {
        return when (methodName) {
            BuiltInOperatorNames.LESS -> "<"
            BuiltInOperatorNames.LESS_OR_EQUAL -> "<="
            BuiltInOperatorNames.GREATER -> ">"
            BuiltInOperatorNames.GREATER_OR_EQUAL -> ">="
            BuiltInOperatorNames.EQEQ -> "=="
            BuiltInOperatorNames.EQEQEQ -> "==="
            BuiltInOperatorNames.IEEE754_EQUALS -> "=="
            BuiltInOperatorNames.ANDAND -> "&&"
            BuiltInOperatorNames.OROR -> "||"
            else -> null
        }
    }

    private fun String.addKotlinPackage(): String =
        if (this == "T" || this == "T0?") this else "kotlin.$this"

    private fun castValue(name: String, type: String): String = when (type) {
        "Any?", "T" -> name
        "Array" -> "$name as Array<Any?>"
        "Comparable" -> "$name as Comparable<Any?>"
        else -> "$name as $type"
    }

    private fun castValueParenthesized(name: String, type: String): String =
        if (type == "Any?") name else "(${castValue(name, type)})"
}

private fun getOperationMap(argumentsCount: Int): MutableList<Operation> {
    val builtIns = DefaultBuiltIns.Instance
    val operationMap = mutableListOf<Operation>()
    val allPrimitiveTypes = PrimitiveType.values().map { builtIns.getBuiltInClassByFqName(it.typeFqName) }
    val arrays = PrimitiveType.values().map { builtIns.getPrimitiveArrayClassDescriptor(it) } + builtIns.array
    val additionalBuiltIns = listOf(
        builtIns.string, builtIns.any, builtIns.charSequence, builtIns.number, builtIns.comparable, builtIns.throwable
    )

    fun CallableDescriptor.isFakeOverride(classDescriptor: ClassDescriptor): Boolean {
        val isPrimitive = KotlinBuiltIns.isPrimitiveClass(classDescriptor) || KotlinBuiltIns.isString(classDescriptor.defaultType)
        val isFakeOverridden = (this as? FunctionDescriptor)?.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
        return !isPrimitive && isFakeOverridden
    }

    val excludedBinaryOperations = listOf("rangeUntil").map { Name.identifier(it) }

    for (classDescriptor in allPrimitiveTypes + additionalBuiltIns + arrays) {
        val compileTimeFunctions = classDescriptor.unsubstitutedMemberScope.getContributedDescriptors()
            .filterIsInstance<CallableDescriptor>()
            .filter { !it.isFakeOverride(classDescriptor) && it.valueParameters.size + 1 == argumentsCount }
            .filter { it.name !in excludedBinaryOperations }

        for (function in compileTimeFunctions) {
            val parameterTypes = listOf(classDescriptor.defaultType.constructor.toString()) +
                    function.valueParameters.map { it.type.toString() }
            operationMap.add(Operation(function.name.asString(), parameterTypes, function is FunctionDescriptor))
        }
    }

    val unsignedClasses = listOf(UInt::class, ULong::class, UByte::class, UShort::class)
    val excludedUnsignedOperations = listOf("dec", "hashCode", "inc", "rangeTo", "rangeUntil")
    for (unsignedClass in unsignedClasses) {
        unsignedClass.memberFunctions
            .filter { it.parameters.size == argumentsCount }
            .filter { !excludedUnsignedOperations.contains(it.name) }
            .forEach { function ->
                operationMap.add(Operation(function.name, function.parameters.map {
                    it.type.toString().removePrefix("kotlin.")
                }))
            }
    }

    return operationMap
}

private fun getUnsignedConversionOperationMap(): List<Operation> {
    val operationMap = mutableListOf<Operation>()

    val fullList = listOf("toULong", "toUInt", "toUShort", "toUByte")
    val uintConversionExtensions = mapOf(
        "Long" to fullList,
        "Int" to fullList,
        "Short" to fullList,
        "Byte" to fullList,
        "Double" to listOf("toULong", "toUInt"),
        "Float" to listOf("toULong", "toUInt"),
    )

    for ((type, extensions) in uintConversionExtensions) {
        for (extension in extensions) {
            operationMap.add(Operation(extension, listOf(type)))
        }
    }

    return operationMap
}

private fun getBinaryIrOperationMap(): List<Operation> {
    val operationMap = mutableListOf<Operation>()

    fun addOperation(function: String, type: String) {
        operationMap.add(Operation(function, listOf(type, type)))
    }

    val compareFunction = setOf(
        BuiltInOperatorNames.LESS, BuiltInOperatorNames.LESS_OR_EQUAL, BuiltInOperatorNames.GREATER, BuiltInOperatorNames.GREATER_OR_EQUAL
    )

    for (function in compareFunction) {
        for (type in numericTypes) {
            addOperation(function, type)
        }
    }

    addOperation(BuiltInOperatorNames.EQEQ, "Any?")
    addOperation(BuiltInOperatorNames.EQEQEQ, "Any?")

    for (type in fpTypes) {
        addOperation(BuiltInOperatorNames.IEEE754_EQUALS, "$type?")
    }

    for (function in setOf(BuiltInOperatorNames.ANDAND, BuiltInOperatorNames.OROR)) {
        addOperation(function, PrimitiveType.BOOLEAN.typeName.asString())
    }

    return operationMap
}

// TODO can be drop after serialization introduction
private fun getExtensionOperationMap(): List<Operation> {
    val operationMap = mutableListOf<Operation>()

    for (type in integerTypes) {
        for (otherType in integerTypes) {
            operationMap.add(Operation("mod", listOf(type, otherType)))
            operationMap.add(Operation("floorDiv", listOf(type, otherType)))
        }
    }

    for (type in fpTypes) {
        for (otherType in fpTypes) {
            operationMap.add(Operation("mod", listOf(type, otherType)))
        }
    }

    return operationMap
}

private fun String.typeSortKey() =
    listOf("Boolean", "Char", "Byte", "Short", "Int", "Float", "Long", "Double", "Number", "UByte", "UShort", "UInt", "ULong", "String", "CharSequence", "Comparable", "Any", "Any?", "Unit", "Throwable")
        .map { "kotlin.$it" }
        .indexOf(this)